﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Principal;
using System.Security.Authentication;
using EnCor.Config;
using EnCor.Caching;
using EnCor.ObjectBuilder;
using System.Threading;
using EnCor.Logging;

namespace EnCor.Security
{
    public sealed class AuthenticationCenter
    {
        #region Private Members
        private AuthenticationProviderCollection _AuthenticationProviders;

        private AuthenticationProviderFactory _Factory = new AuthenticationProviderFactory();

        private LoginCacheManager LoginCacheManager;

        private IdentityMapperCollection _Mappers;

        private ICacheManager AuthenticationProvidersCache;

        private IEnumerable<ITransferCredentialSerializer> _TransferCredentialSerializers;

        private ITokenProvider _TokenProvider;

        private AuthenticationAdapterCollection _Adapters;

        private ILog _Logger;


        #endregion

        #region Constructors


        internal AuthenticationCenter(AuthenticationConfig config, IdentityMapperCollection mappers)
        {
            _Logger = LogManager.GetLogger(this.GetType().Name);

            BuilderContext builderContext = new BuilderContext();

            // Setup extensions
            List<AuthenticationExtension> extensions = new List<AuthenticationExtension>();
            AuthenticationExtensionFactory extensionFactory = new AuthenticationExtensionFactory();
            foreach (AuthenticationExtensionConfig extensionConfig in config.Extensions)
            {
                extensions.Add(extensionFactory.Create(builderContext, extensionConfig));
            }

            // Setup builder context
            foreach (AuthenticationExtension extension in extensions)
            {
                extension.AddBuilderContext(builderContext);
            }

            // build authentication providers
            IList<IAuthenticationProvider> providers = BuildAuthenticationProviders(config.Providers, builderContext);
            _AuthenticationProviders = new AuthenticationProviderCollection(providers);

            // build token provider
            _TokenProvider = BuildTokenProvider(config.TokenProviders);

            // verify mappers
            VerifyAuthenticationProviderMappers(providers, mappers);


            // Authentication Adapters
            _Adapters = new AuthenticationAdapterCollection(BuildAdapters(config.Adapters));


        }

        #endregion

        private ITokenProvider BuildTokenProvider(TokenProviderConfigCollection tokenProviderConfigCollection)
        {
            TokenProviderFactory factory = new TokenProviderFactory();
            Dictionary<string, ITokenProvider> providers = new Dictionary<string, ITokenProvider>();
            foreach (TokenProviderConfig config in tokenProviderConfigCollection)
            {
                providers.Add(config.Name, factory.Create(null, config));
            }
            if (providers.Count == 0)
            {
                providers.Add(tokenProviderConfigCollection.ProviderName, new MemoryCachedTokenProvider());
            }

            return providers[tokenProviderConfigCollection.ProviderName];
        }

        private void VerifyAuthenticationProviderMappers(IList<IAuthenticationProvider> providers, IdentityMapperCollection mappers)
        {
            foreach (IAuthenticationProvider provider in providers)
            {
                if ( !string.IsNullOrEmpty(provider.Mapper) && mappers.GetMapper(provider.Mapper) == null)
                {
                    throw new EnCorException(string.Format("Cannot find IdentityMapper for provider {0}, expected mapper name is {1}", provider, provider.Mapper));
                }
            }
            _Mappers = mappers;
        }

        #region Private Methods

        private IAuthenticationProvider GetProviderForCredential(Credential credential)
        {
            if (credential.Type == CredentialTypes.TokenCredential)
            {

            }
            IAuthenticationProvider provider = _AuthenticationProviders.GetAuthenticationProvider(credential.Type, credential.Tag);

            return provider;
        }

        private ITransferCredentialSerializer GetSerializerForTransferCredential(TransferCredential credential)
        {
            foreach (ITransferCredentialSerializer serializer in _TransferCredentialSerializers)
            {
                if (serializer.SupportCredentialType(credential.CredentialType))
                {
                    return serializer;
                }
            }
            return null;
        }

        private IList<IAuthenticationProvider> BuildAuthenticationProviders(AuthenticationProviderConfigCollection config, BuilderContext builderContext)
        {
            List<IAuthenticationProvider> providers = new List<IAuthenticationProvider>();
            AuthenticationProviderFactory factory = new AuthenticationProviderFactory();
            foreach (AuthenticationProviderConfig authenticationProviderConfig in config)
            {
                IAuthenticationProvider provider = factory.Create(builderContext, authenticationProviderConfig);
                provider.Mapper = authenticationProviderConfig.Mapper;
                providers.Add(provider);
            }
            return providers;
        }


        private string GenerateTokenString(EnCorIdentity identity)
        {
            string tokenString = string.Empty;
            if (_TokenProvider != null)
            {
                tokenString = _TokenProvider.GenerateToken(identity);
            }
            return tokenString;
        }

        private IList<IAuthenticationAdapter> BuildAdapters(AuthenticationAdapterConfigCollection config)
        {
            List<IAuthenticationAdapter> adapters = new List<IAuthenticationAdapter>();
            AuthenticationAdapterFactory factory = new AuthenticationAdapterFactory();
            foreach (AuthenticationAdapterConfig c in config)
            {
                IAuthenticationAdapter adapter = factory.Create(null, c);
                adapters.Add(adapter);
            }
            return adapters;
        }


        #endregion

        #region Public Methods

        public event EventHandler<AuthenticateNotifyEventArgs> UserAuthenticated;

        private void RaiseUserAuthenticated(EnCorIdentity identity)
        {
            if (UserAuthenticated != null)
            {
                UserAuthenticated(this, new AuthenticateNotifyEventArgs(identity));
            }
        }

        public AuthenticateToken Authenticate(TransferCredential transferCredential)
        {
            ITransferCredentialSerializer serializer = GetSerializerForTransferCredential(transferCredential);
            if (serializer == null)
            {
                throw new EnCorException(string.Format("Can not find serializer of Credential type : {0}", transferCredential.CredentialType));
            }

            Credential credential = serializer.Deserialize(transferCredential);
            return Authenticate(credential);
        }

        public AuthenticateToken Authenticate(Credential credential)
        {
            IAuthenticationProvider provider = GetProviderForCredential(credential);
            if (provider == null)
            {
                //throw new EnCorException(string.Format("Can not find AuthenticationProvider for Credential type :{0}", credential.Type));
                _Logger.Warn(string.Format("Can not find AuthenticationProvider for Credential type :{0}", credential.Type));
            }

            UserIdentity identity = provider.Authenticate(credential);
            if (identity == null)
            {
                return null;
            }
            IIdentityMapper mapper = GetIdentityMapper(provider);
            if (mapper == null)
            {
                mapper = new SingleIdentityMapper();
                //throw new EnCorException(string.Format("Cannot get IdentityMapper for provider: {0}", provider));
            }
            UserIdentity globalId = mapper.GetGlobalIdentity(identity);
            if (globalId == null)
            {
                return null;
            }
            EnCorIdentity encorIdentity = new EnCorIdentity(globalId.Id, globalId.Name, provider.CredentialType, true);
            string tokenString = GenerateTokenString(encorIdentity);
            RaiseUserAuthenticated(encorIdentity);

            AuthenticateToken authenticateToken = new AuthenticateToken(encorIdentity, tokenString);
            SetAdapterCredential(authenticateToken);
            return authenticateToken;
        }

        private void SetAdapterCredential(AuthenticateToken token)
        {
            foreach (IAuthenticationAdapter adapter in _Adapters)
            {
                if (!adapter.IsReadOnly)
                {
                    adapter.SetCredential(token);
                }
            }
        }

        private IIdentityMapper GetIdentityMapper(IAuthenticationProvider authenticationProvider)
        {
            return _Mappers.GetMapper(authenticationProvider.Mapper);
        }

        #endregion

        internal EnCorIdentity AuthenticateCurrentUser()
        {
            foreach (IAuthenticationAdapter adapter in _Adapters)
            {
                Credential currentCredential = adapter.GetCredential();
                if (currentCredential == null)
                {
                    continue;
                }

                if (currentCredential is TokenCredential)
                {
                    TokenCredential tokenCredential = currentCredential as TokenCredential;
                    EnCorIdentity identity = _TokenProvider.GetIdentitybyToken(tokenCredential.Token);
                    if (identity != null)
                    {
                        RaiseUserAuthenticated(identity);
                    }
                    return identity;
                }

                AuthenticateToken token = Authenticate(currentCredential);
                if (token != null)
                {
                    return token.Identity;
                }


            }
            return null;
        }

        internal void LogoffCurrentUser()
        {
            foreach (IAuthenticationAdapter adapter in _Adapters)
            {
                if (!adapter.IsReadOnly)
                    adapter.ClearCredential();
            }
        }
    }
}
